package framework.tables;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;

import junit.framework.Assert;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;

import framework.BaseEntity;
import framework.CommonFunctions;
import framework.elements.Label;
import framework.elements.Link;
import framework.interfaces.TabTablesInterface;

/**
 * Базовый класс для работы с таблицами
 */
public class BaseTable extends BaseEntity implements TabTablesInterface{

	/**
	 * @param tableClassName название класса таблицы
 	 * @param childOfTagTH дочерний элемент тега заголовка таблицы <th>
 	 * Например '/strong', тогда локатор к назв. колонки '//th/strong'
 	 * @param childOfTagTD путь к дочернему элементу, содержащему искомый текст
 	 * Например '/span'
	 */
	public BaseTable(final String tableClassName, final String childOfTagTH, final String childOfTagTD){
		this.className = tableClassName;
		this.childOfTagTh = childOfTagTH;
		this.childOfTagTd = childOfTagTD;
	}

	private final String className, childOfTagTh, childOfTagTd;
	private String tableHeaderTemplate = "//table[@class='%s']/thead/tr/th[%s]";
	private String tableRowTemplate = "//table[@class='%s']/tbody/tr[%s]/td[%s]";
	private final String contains = "[contains(.,'%s')]";
	private final String epLinkTemplate = "/img[contains(@src,'ep.png')]";
	private final Link lnkNext = new Link(By.xpath("//a[contains(.,'следующая')]"),"следующая");
	public final Link lnkPrevious = new Link(By.xpath("//a[contains(.,'предыдущая')]"),"предыдущая");
	private static HashMap<String, Integer> columns = new HashMap<String, Integer>();
	private int countOfTrying;
	// Работа с проверкой даты и времени
	private final int dateAndTimeLength = 14;
	private final int timesArrayLength = 12;

	/** Возвращает локатор к картинке электронной подписи
	 * @return локатор
	 */
	protected final String getEpLinkXpathImg(){
		return epLinkTemplate;
	}

	/**
	 * Создает хэш таблицу названий колонок таблицы и их номера по порядку
	 */
	public final void initializeTable(){
		if(columns.size() != 0) {
			return;
		}else{
			// Иногда таблицы могут не содержать тега <thead>
			// В таком случае названия колонок таблицы находятся в /tbody/tr[1]
			// Механизм ниже проверяет описанную выше ситуацию и изменяет tableHeaderTemplate
			Label label = new Label(By.xpath(String.format("//table[@class='%s']/thead", className)), "Таблица содержит <thead>");
			if(!label.isPresent()){
				label = new Label(By.xpath(String.format("//table[@class='%s']/tbody/tr[1]/th", className)), "Таблица не содержит тег <thead>");
				if(label.isPresent()){
					tableHeaderTemplate = "//table[@class='%s']/tbody/tr[1]/th[%s]";
				}else{
					tableHeaderTemplate = "//table[@class='%s']/tbody/tr[1]/td[%s]";
				}
			}
		}
		Label cName = null;

		int c = browser.getDriver().findElementsByXPath(String.format(tableHeaderTemplate, className, "").replaceAll("\\[\\]", "")).size();
		if (c>0) {
			countOfTrying = c + 1;
		}else{
			logger.fatal("Не удалось инициализировать таблицу. Локатор инициализации: " + String.format(tableHeaderTemplate, className, "").replaceAll("\\[\\]", ""));
		}
		for(int i = 1; i < countOfTrying; i++){

			cName = new Label(By.xpath(String.format(tableHeaderTemplate, className, i) + childOfTagTh), "Колонка " + i);
			if(cName.isPresent()) {
				columns.put(cName.getElement().getText(), i);
			}
		}
		//printTableColumns();
	}

	/** Очищает хэш таблицу номеров колонок
	 */
	public static void clearHashMap(){
		columns.clear();
	}

	/** Выводит в лог названия колонок и их номера
	 */
	private void printTableColumns(){
		initializeTable();
		for(String cName : columns.keySet()){
			logger.info("Колонка " + cName + " имеет порядковый номер: " + columns.get(cName).toString());
		}
	}

	/** Возвращает номер строки, в которой находится текст.
	 * @param text текст
	 * @param column кололонка, в которой находится текст
	 * @return номер строки
	 */
	private Integer getRowIndexWithText(final String text, final String column){
		initializeTable();
		Label l = null;
		int countOfRow = browser.getDriver().findElementsByXPath(String.format(tableRowTemplate.substring(0,tableRowTemplate.indexOf("[%s]/td")), className)).size();
		for(int i = 1; i <= countOfRow; i++){
			l = new Label(By.xpath(String.format(tableRowTemplate, className, i, columns.get(column)) + childOfTagTd + String.format(contains, text)), "Ячейка таблицы(столбец:строка) =  " + columns.get(column) + ":" + i);
			if(l.waitForIsElementWithoutAssertion()) {
				return i;
			}
		}
		logger.fatal("В колонке " + column + " таблицы ни в одной строке нет текста: " + text);
		return 0;
	}

	/**
	 * Возвращает текст из ячейки
	 * @param column название колонки из которой нужен текст
	 * @param unicalTextInRow имееющийся текст в строке
	 * @param columnWithUnicalText колонка, в которой находится имеющийся текст
	 * @return текст ячейки.
	 */
	public final String getText(final String column, final String unicalTextInRow, final String columnWithUnicalText){
		initializeTable();
		// Сначала делается попытка получения текста из строки напрямую
		Label l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]/td[%s]", "/tr[contains(.,'%s')]/td[%s]"), className, unicalTextInRow, columns.get(column))), column);
		if(l.isPresent()){
			return l.getText();
		}
		// Если не удалось получить текст из строку напрямую
		l = new Label(By.xpath(String.format(tableRowTemplate, className, getRowIndexWithText(unicalTextInRow, columnWithUnicalText), columns.get(column)) + childOfTagTd), "Колонка " + column);
		l.waitAndAssertIsPresent();
		String text = l.getText();
		return text;
	}

	/** Возвращает текст из ячейки.
	 * @param column колонка с текстом
	 * @param textInRow текст, присутствующий в той же строке
	 * @param columnNameWithText колонка в которой присутствует textInRow
	 * @param childOfTd если текст находится внутри ссылки или другого
	 * внутреннего тега для <td>
	 * @return текст ячейки
	 */
	public final String getText(final String column, final String textInRow, final String columnNameWithText, final String childOfTd){
		initializeTable();
		// Сначала делается попытка получения текста из строки напрямую
		Label l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]/td[%s]", "/tr[contains(.,'%s')]/td[%s]" + childOfTd), className, textInRow, columns.get(column))), column);
		if(l.isPresent()){
			return l.getText();
		}
		// Если текст в строке не удалось найти, конкретизируем локатор
		l = new Label(By.xpath(String.format(tableRowTemplate, className, getRowIndexWithText(textInRow, columnNameWithText), columns.get(column)) + childOfTd), "Колонка " + column);
		if(!l.isPresent()){
			System.out.println(l.getLocator().toString());
		}
		l.waitAndAssertIsPresent();
		String text = l.getText();
		return text;
	}

	/**
	 * Возвращает текст из строки, которая простирается на все колонки
	 * @param expectedText часть ожидаемого текста в это строке
	 * @return текст строки таблицы
	 */
	public final String getTextFromRowWithoutColumns(final String expectedText){
		initializeTable();
		Label l = new Label(By.xpath(String.format(tableRowTemplate, className, getRowIndexWithText(expectedText, getColumnNameByIndex(1)), 1) + childOfTagTd), "");
		String text = l.getText();
		logger.info("Запись содержит текст: " + text);
		return text;
	}

	/** Получает имя колонки по ее номеру
	 * @param index номер
	 * @return название колонки
	 */
	public String getColumnNameByIndex(final Integer index){
		initializeTable();
		for(Entry<String, Integer> entry: columns.entrySet()){
			if(entry.getValue() == 1) {
				return entry.getKey();
			}
		}
		return null;
	}

	/** Возвращает локатор к ячейке таблицы
	 * @param columnName колонка в которой находится ячейка
	 * @param columnWithExpectedText текст, который находится с ячейкой в одной строке
	 * @param expectedTextInRow название колонки с текстом
	 * @return локатор
	 */
	public final String getXpathLocatorForCell(final String columnName, final String columnWithExpectedText, final String expectedTextInRow){
		initializeTable();
		// Сначала делаем попытку найти текст в строке напрямую
		Label l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]/td[%s]", "//tr[contains(.,'%s')]/td[%s]"), className, expectedTextInRow, columns.get(columnName))), "Строка с текстом: " + expectedTextInRow);	
		while((!l.isPresent())&&lnkNext.isPresent()) {
			lnkNext.jsClick();
			browser.waitForPageToLoad();
		}
		if(l.isPresent()){
			//if(l.getText().equals(expectedTextInRow)){
				return l.getLocator().toString().replace("By.xpath: ","");
			//}
		}
		return String.format(tableRowTemplate, className, getRowIndexWithText(expectedTextInRow, columnWithExpectedText), columns.get(columnName));
	}

	/** Проверяет текст в колонке
	 * @param column колонка
	 * @param expected ожидаемый текст
	 * @param columnWithUnicalText колонка в которой находится существующий текст в той же строке
	 * @param unicalTextInRow существующий текст в той же строке
	 */
	public final void assertTextInColumn(final String column, final String expected,
			final String columnWithUnicalText, final String unicalTextInRow) {
		initializeTable();
		String actual = getText(column, unicalTextInRow, columnWithUnicalText);
		jassert.assertText(column, expected, actual, true);
	}

	/** Проверяет текст в колонке (содержит ли?)
	 * @param column колонка
	 * @param expected ожидаемый текст
	 * @param columnWithUnicalText колонка в которой находится существующий текст в той же строке
	 * @param unicalTextInRow существующий текст в той же строке
	 */
	public final void assertTextContainsInColumn(final String column, final String expected,
			final String columnWithUnicalText, final String unicalTextInRow) {
		initializeTable();
		String actual = getText(column, unicalTextInRow, columnWithUnicalText);
		jassert.assertTextContains(column, expected, actual, true);
	}

	/** Проверяет текст в колонке
	 * @param column колонка
	 * @param expected ожидаемый текст
	 * @param columnWithExistedText колонка в которой находится существующий текст в той же строке
	 * @param existedText существующий текст в той же строке
	 * @param childOfTd дополнение к локатору ячейки <td>, например "/p[1]"
	 */
	public final void assertTextInColumn(final String column, final String expected,
			final String columnWithExistedText, final String existedText, String childOfTd) {
		initializeTable();
		String actual = getText(column, existedText, columnWithExistedText, childOfTd);
		jassert.assertText(column, expected, actual, true);
	}

	/**
	 * @deprecated используйте лучше {@link #assertDateAndTime(String columnWithDateAndTime, String date, String time,
			String unicalTextInRow, String columnWithUnicalText, String childOfTd)}
	 * Проверяет дату и время в таблице с колонкой "Версия"
	 * @param date дата
	 * @param time время
	 * @param version версия
	 */
	public void assertDateAndTime(String date, String time, String version){
		String actualText =  getText(COLUMN_DATA_IZMENENIYA, version, COLUMN_VERSIYA);
		//Если длина даты больше 14, то значит она содержит и время
		if(actualText.length() > dateAndTimeLength){
			String[] times = new String[timesArrayLength];
			for(int i = -(timesArrayLength/2 - 1); i < timesArrayLength/2; i++){
				try{
					SimpleDateFormat df = new SimpleDateFormat("HH:mm");
			        Date d = df.parse(time);
			        Calendar cal = Calendar.getInstance();
			        cal.setTime(d);
			        cal.add(Calendar.MINUTE, i);
			        d = cal.getTime();
			        times[i + (timesArrayLength/2 - 1)] = df.format(d);
			    }catch(ParseException e){
					info(e.getMessage());
				}
			}
			for(int k = 0; k < timesArrayLength; k++){
				if(actualText.equals(date + " " + times[k] + " (МСК)")){
					logger.info("Дата и время публикации электронной подписи является корректной: " + actualText);
					return;
				}
			}
			Assert.assertEquals("Дата изменения отображается некорректно.", date + " " + time + " (МСК)", actualText);
			logger.info("Дата изменения отображается корректно: " + date + " (МСК)");
		}else{
			assertTextInColumn(COLUMN_DATA_IZMENENIYA, date + " (МСК)", COLUMN_VERSIYA, version);
			logger.info("Дата изменения отображается корректно: " + date + " (МСК)");
		}
	}

	/** Проверяет дату и время(с расхождением +(-)3 минуты)
	 * @param columnWithDateAndTime наименование колонки с датой и временем
	 * @param date дата
	 * @param time время
	 * @param unicalTextInRow уникальный текст в той же строке
	 * @param columnWithUnicalText колонка с уникальным текстом
	 * @param childOfTd вложенный элемент в ячейке с датой, например "/span"
	 */
	public void assertDateAndTime(String columnWithDateAndTime, String date, String time,
			String unicalTextInRow, String columnWithUnicalText, String childOfTd){
		String actual = getText(columnWithDateAndTime, unicalTextInRow, columnWithUnicalText, childOfTd);
		//Если длина даты больше 14, то значит она содержит и время
		if(actual.length() > dateAndTimeLength){
			String[] times = new String[timesArrayLength];
			for(int i = -(timesArrayLength/2 - 1); i < timesArrayLength/2; i++){
				try{
					SimpleDateFormat df = new SimpleDateFormat("HH:mm");
			        Date d = df.parse(time);
			        Calendar cal = Calendar.getInstance();
			        cal.setTime(d);
			        cal.add(Calendar.MINUTE, i);
			        d = cal.getTime();
			        times[i + (timesArrayLength/2 - 1)] = df.format(d);
			    }catch(ParseException e){
					info(e.getMessage());
				}
			}
			for(int k = 0; k < timesArrayLength; k++){
				if(actual.equals(date + " " + times[k]  + " (МСК)")){
					logger.info("Дата и время являются корректными: " + actual);
					return;
				}
			}
			jassert.assertText("Дата и время изменения", date + " " + time  + " (МСК)", actual, true);
		}else{
			assertTextInColumn(columnWithDateAndTime, date  + " (МСК)", columnWithUnicalText, unicalTextInRow, childOfTd);
			logger.info("Дата изменения отображается корректно: " + date);
		}
	}

	/** Проверяет значение колонки таблицы.
	 *  Применяется, если количество колонок в header таблицы отличается от этого количества в строке
	 * @param columnWithUnicalText колонка с уникальным текстом в той же строке с проверяемой ячейкой
	 * @param unicalText уникальный текст
	 * @param verifiableColumn номер проверяемой колонки
	 * @param verifiableColumnName наименование проверяемой колонки
	 * @param expected ожидаемое значение в ячейке
	 * @param childOfTd вложенный элемент в ячейку, например "/span"
	 */
	public void assertCell(String columnWithUnicalText, String unicalText, int verifiableColumn,
			String verifiableColumnName, String expected, String childOfTd){
		String actual = getText(columnWithUnicalText, unicalText, verifiableColumn, verifiableColumnName, childOfTd);
		jassert.assertText(verifiableColumnName, expected, actual, true);
	}

	/** Возвращает значение ячейки таблицы.
	 *  Применяется, если количество колонок в header таблицы отличается от этого количества в строке
	 * @param columnWithUnicalText колонка с уникальным текстом в той же строке с проверяемой ячейкой
	 * @param unicalText уникальный текст
	 * @param verifiableColumn номер проверяемой колонки
	 * @param verifiableColumnName наименование проверяемой колонки
	 * @param childOfTd вложенный элемент в ячейку, например "/span"
	 * @return значение ячейки
	 */
	public String getText(String columnWithUnicalText, String unicalText, int verifiableColumn,
			String verifiableColumnName, String childOfTd){
			String xpath = getXpathLocatorForCell(columnWithUnicalText, columnWithUnicalText, unicalText);
			xpath = xpath.substring(0, xpath.indexOf("/td[")) + "/td[%s]" + childOfTd;
			Label l = new Label(By.xpath(String.format(xpath, verifiableColumn)), verifiableColumnName);
			return l.getText();
	}

	/** Проверяет дату/дату и время в ячейке.
	 *  Применяется, если количество колонок в header таблицы отличается от этого количества в строке
	 * @param columnWithUnicalText колонка с уникальным текстом в той же строке с проверяемой ячейкой
	 * @param unicalText уникальный текст
	 * @param dateAndTimeColumn номер проверяемой колонки
	 * @param dateAndTimeColumnName наименование проверяемой колонки
	 * @param date дата
	 * @param time время
	 * @param childOfTd вложенный элемент в ячейку, например "/span"
	 */
	public void assertDateAndTimeCell(String columnWithUnicalText, String unicalText, int dateAndTimeColumn,
			String dateAndTimeColumnName, String date, String time, String childOfTd){
		String actual = getText(columnWithUnicalText, unicalText, dateAndTimeColumn, dateAndTimeColumnName, childOfTd);
		//Если длина даты больше 14, то значит она содержит и время
		if(actual.length() > dateAndTimeLength){
			String[] times = CommonFunctions.getTimesArray(time, timesArrayLength/2);
			jassert.assertTime(date + " %s (МСК)", times, actual, "Дата и время изменения");
		}else{
			assertCell(columnWithUnicalText, unicalText, dateAndTimeColumn, dateAndTimeColumnName, date + " (МСК)", childOfTd);
			logger.info("Дата изменения отображается корректно: " + date + " (МСК)");
		}
	}

	/* Ниже находятся методы, для работы с таблицами, у которых содержимое строк отличается только тем,
	 * что в одной строке колонка не содержит значения(пусто)*/

	/** Получаем количество строк по определенному признаку:
	 *  @param trSign признак - содержимое тега <tr>(tr[%s]). Например "@class='xxxx'" или "contains(@class,'some text')"
	 *	@return количество строк
	 */
	private int getInfoRowCount(String trSign){
		return browser.getDriver().findElementsByXPath(String.format(tableRowTemplate.replace("/td[%s]", ""), className, trSign)).size();
	}

	/** Возвращает номер строки по тексту в колонке и признаку строки
	 * @param trSign признак - содержимое тега <tr>(tr[%s]). Например "@class='xxxx'" или "contains(@class,'some text')"
	 * @param columnName колонка с текстом в таблице
	 * @param columnText текст в этой колонке
	 * @return номер строки
	 */
	private int getRowNumber(String trSign, String columnName, String columnText){
		initializeTable();
		int count = getInfoRowCount(trSign);
		Label l;
		for(int i = 1; i <= count; i++){
			l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]", "/tr[%s][%s]"), className, trSign, i, columns.get(columnName))), "Проверяемая строка: '" + i +"'");
			try{
				if(l.getElement().getText().equals(columnText)){
					return i;
				}
			}catch(NoSuchElementException e){
				continue;
			}
		}
		logger.fatal(String.format("В таблице нет ни одной строки с текстом '%s' в колонке '%s'!", columnText, columnName));
		return 0;
	}

	/** Возвращает текст из колонки
	 * @param columnName название колонки
	 * @param childOfTd дочерний элемент ячейки, например "/p"
	 * @param trSign признак - содержимое тега <tr>(tr[%s]). Например "@class='xxxx'" или "contains(@class,'some text')"
	 * @param columnWithUnicalText колонка с текстом в той же строке
	 * @param unicalText текст этой колонки
	 * @return текст из колонки
	 */
	public String getTextFromColumn(String columnName, String childOfTd, String trSign, String columnWithUnicalText, String unicalText){
		int rowNumber = getRowNumber(trSign, columnWithUnicalText, unicalText);
		Label l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]", "/tr[%s][%s]") + childOfTd, className, trSign, rowNumber, columns.get(columnName))),
				String.format("Колонка '%s' строки '%s'", columnName, rowNumber));
		return l.getElement().getText();
	}

	/** Проверяет текст в колонке
	 * @param columnName название колонки
	 * @param expected ожидаемый текст в колонке
	 * @param childOfTd дочерний элемент ячейки, например "/p"
	 * @param trSign признак - содержимое тега <tr>(tr[%s]). Например "@class='xxxx'" или "contains(@class,'some text')"
	 * @param columnWithUnicalText колонка с текстом в той же строке
	 * @param unicalText текст этой колонки
	 */
	public void assertTextFromColumn(String columnName, String expected, String childOfTd, String trSign, String columnWithUnicalText, String unicalText){
		String actual = getTextFromColumn(columnName, childOfTd, trSign, columnWithUnicalText, unicalText);
		jassert.assertText(String.format("Проверяемый текст в колонке '%s'", columnName), expected, actual, true);
	}

	/** Возвращает локатор к ячейке таблицы
	 * @param columnName название колонки
	 * @param childOfTd дочерний элемент ячейки, например "/p"
	 * @param trSign признак - содержимое тега <tr>(tr[%s]). Например "@class='xxxx'" или "contains(@class,'some text')"
	 * @param columnWithUnicalText колонка с текстом в той же строке
	 * @param unicalText текст этой колонки
	 * @return локатор к ячейке таблицы
	 */
	public String getXpathLocatorFromCell(String columnName, String childOfTd, String trSign, String columnWithUnicalText, String unicalText){
		int rowNumber = getRowNumber(trSign, columnWithUnicalText, unicalText);
		Label l = new Label(By.xpath(String.format(tableRowTemplate.replace("/tr[%s]", "/tr[%s][%s]") + childOfTd, className, trSign, rowNumber, columns.get(columnName))),
				String.format("Колонка '%s' строки '%s'", columnName, rowNumber));
		return l.getLocator().toString().replace("By.xpath: ", "");
	}

	@Override
	protected final String formatLogMsg(final String message) {
		return null;
	}
}
